Skip to content

sync agent rule - #476

Merged
iceljc merged 1 commit into
SciSharp:mainfrom
iceljc:features/add-rule-criteria
Aug 12, 2026
Merged

sync agent rule#476
iceljc merged 1 commit into
SciSharp:mainfrom
iceljc:features/add-rule-criteria

Conversation

@iceljc

@iceljc iceljc commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add agent rule criteria mode, message, and code-scripts deep link

✨ Enhancement 🐞 Bug fix 🕐 20-40 Minutes

Grey Divider

AI Description

• Add per-rule message, criteria text, and selectable criteria mode with trigger defaults.
• Deep-link from rules to code-scripts with agent preselected and tab session restoration.
• Improve rule trigger selection UX and align rule-options API typings.
Diagram

graph TD
  A["Agent Rules UI"] -->|"fetch rule options"| C["Agent service"] --> F["Backend endpoints"]
  A -->|"criteria types"| B["Enums & types"]
  A -->|"open new tab"| D["Code Scripts page"] -->|"restore session"| E[(sessionStorage)]
  D -->|"load scripts"| C
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Move auth from sessionStorage to cookie-based session
  • ➕ Eliminates per-tab session cloning issues entirely
  • ➕ Avoids relying on opener access/window.open behavior
  • ➖ May require backend changes (cookie session/CSRF posture)
  • ➖ Potentially larger migration depending on current auth model
2. Use localStorage + storage events/BroadcastChannel for session sync
  • ➕ Works without window.opener and across any newly opened tabs
  • ➕ Can support explicit “sync session” handshake
  • ➖ LocalStorage persistence may be undesirable for tokens
  • ➖ Requires careful security review to avoid broadening token exposure
3. Pass short-lived one-time token in the URL when opening new tabs
  • ➕ Doesn’t depend on opener being available/same-origin accessible
  • ➕ Keeps sessionStorage model per-tab
  • ➖ Token-in-URL has leak risks via referrers/history if not handled carefully
  • ➖ Requires backend support for token exchange/expiry

Recommendation: Given the existing per-tab sessionStorage design, restoring session from a same-origin opener is a pragmatic, low-impact fix, and using window.open from the rules UI improves reliability. If cross-tab navigation becomes a broader requirement, consider migrating to cookie-based auth or an explicit cross-tab sync mechanism (BroadcastChannel) to avoid relying on opener semantics.

Files changed (8) +294 / -18

Enhancement (6) +249 / -16
enums.jsAdd RuleCriteriaMode enum +6/-0

Add RuleCriteriaMode enum

• Introduces a new RuleCriteriaMode enum (llm, python_script) for rule criteria handling and UI selection.

src/lib/helpers/enums.js

agentTypes.jsExtend agent rule types with criteria object and rule option catalog +21/-2

Extend agent rule types with criteria object and rule option catalog

• Adds AgentRuleOption (rule-options catalog shape) and RuleCriteria (mode + criteria text). Updates AgentRule to use message + criteria and to carry default_mode derived from trigger options.

src/lib/helpers/types/agentTypes.js

_agent.scssStyle link-like label for criteria deep link +27/-0

Style link-like label for criteria deep link

• Adds .ari-label-link styling (hover/focus-visible) for the “Criteria Text” label when it acts as a link to Code Scripts.

src/lib/styles/pages/_agent.scss

agent-rule-item.svelteAdd message + criteria mode fields and code-scripts deep link +101/-6

Add message + criteria mode fields and code-scripts deep link

• Expands the rule editor UI to include a Message textarea, a Criteria Mode Select (LLM/Python Script), and updates criteria text handling to use rule.criteria. Adds a deep link to the code-scripts page (with agentId) using window.open to preserve an opener for session restoration, and makes trigger selection searchable.

src/routes/page/agent/[agentId]/agent-components/rules/agent-rule-item.svelte

agent-rule.sveltePersist message/criteria and propagate trigger default mode +32/-5

Persist message/criteria and propagate trigger default mode

• Updates rule serialization to send message and normalized criteria (null when blank). Incorporates trigger option mode into each rule as default_mode, updates change handlers to edit nested criteria fields, and passes agentId down for deep linking.

src/routes/page/agent/[agentId]/agent-components/rules/agent-rule.svelte

+page.svelteSync selected agent with URL query param and harden loading +62/-3

Sync selected agent with URL query param and harden loading

• Adds support for preselecting an agent via ?agentId= and keeping the URL in sync when the dropdown changes. Improves error handling to avoid showing stale scripts and guards URL updates by mount state.

src/routes/page/agent/code-scripts/+page.svelte

Bug fix (1) +43 / -0
store.jsRestore sessionStorage from same-origin opener for new tabs +43/-0

Restore sessionStorage from same-origin opener for new tabs

• Adds restoreSessionFromOpener() to copy user/tenant session keys from a same-origin opener tab once. Hooks it into getUserStore(), getTenantId(), and getTenantName() to prevent login redirects when opening in-app pages in a new tab.

src/lib/helpers/store.js

Documentation (1) +2 / -2
agent-service.jsCorrect rule-options service return types +2/-2

Correct rule-options service return types

• Updates JSDoc return types for getAgentRuleOptions*() to return AgentRuleOption[] instead of AgentRule[] to match API intent.

src/lib/services/agent-service.js

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Criteria whitespace preserved 🐞 Bug ≡ Correctness
Description
normalizeCriteria() checks text.trim() to detect a blank criteria, but returns the untrimmed text,
so a whitespace-only criteria is still persisted whenever mode is set. This can produce semantically
blank-but-present criteria values in agent.rules payloads.
Code

src/routes/page/agent/[agentId]/agent-components/rules/agent-rule.svelte[R77-80]

+        return {
+            mode: mode.trim() || null,
+            criteria: text || null
+        };
Evidence
The new normalizeCriteria() logic trims only mode on return; criteria is returned as the
original text even though blankness is judged via text.trim(). This function is used by
fetchRules() to generate the persisted rules payload.

src/routes/page/agent/[agentId]/agent-components/rules/agent-rule.svelte[41-50]
src/routes/page/agent/[agentId]/agent-components/rules/agent-rule.svelte[66-81]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`normalizeCriteria()` trims `mode` but returns `criteria: text || null` without trimming. If the user enters only whitespace in Criteria Text while selecting a mode, the criteria object will be saved with whitespace content.

### Issue Context
`fetchRules()` uses `normalizeCriteria()` to build the rules array that is saved back into `agent.rules`.

### Fix Focus Areas
- src/routes/page/agent/[agentId]/agent-components/rules/agent-rule.svelte[66-81]

### Suggested change
Return `criteria: text.trim() || null` (and optionally set `const trimmedText = text.trim()` once) so whitespace-only criteria does not get persisted.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Opener restore is one-shot 🐞 Bug ☼ Reliability
Description
restoreSessionFromOpener() sets openerSessionRestored=true before copying any keys, so later calls
will not retry copying keys that were missing during the first attempt. This can leave the new tab
without tenant_id/tenant_name (or other keys) if the opener populates them after the first restore
attempt.
Code

src/lib/helpers/store.js[R53-56]

+export function restoreSessionFromOpener() {
+    if (!browser || openerSessionRestored) return;
+    openerSessionRestored = true;
+
Evidence
The new function sets openerSessionRestored = true immediately and never retries. Yet tenant
values can be set after initial page load via setTenantId/setTenantName, and
getTenantId/getTenantName rely on restoreSessionFromOpener for cross-tab restoration.

src/lib/helpers/store.js[40-74]
src/lib/helpers/store.js[101-128]
src/lib/helpers/store.js[108-147]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`restoreSessionFromOpener()` is guarded by a global `openerSessionRestored` flag that is set to `true` before attempting any key copies. If the first call occurs before the opener has populated some keys (e.g., tenant_id/tenant_name set later), subsequent calls will never attempt to copy those missing keys.

### Issue Context
`getUserStore()` calls this only when `user` is absent; `getTenantId()`/`getTenantName()` call it unconditionally, but are blocked after the first attempt.

### Fix Focus Areas
- src/lib/helpers/store.js[40-74]
- src/lib/helpers/store.js[101-128]

### Suggested change
Allow repeated attempts to copy *still-missing* keys without overwriting existing values. For example:
- Remove the global one-shot flag entirely, or
- Replace it with per-key tracking (e.g., `restoredKeys` set) and only skip keys already present or already successfully restored, or
- Only set `openerSessionRestored=true` after a same-origin opener is confirmed and the copy loop has run, and allow retrying when any of `[userKey, tenantKey, tenantNameKey]` remains missing.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Opener kept for new tab 🐞 Bug ⛨ Security
Description
openCodeScripts() uses window.open(url, '_blank') specifically to preserve window.opener for session
restoration, which means the opened tab can navigate the opener (reverse-tabnabbing/opener
manipulation) if it ever reaches attacker-controlled content. This is a security tradeoff introduced
by relying on opener for credential/session transfer.
Code

src/routes/page/agent/[agentId]/agent-components/rules/agent-rule-item.svelte[R123-126]

+        if (!codeScriptUrl) return;
+
+        e.preventDefault();
+        window.open(codeScriptUrl, '_blank');
Evidence
The new rule UI intercepts the code-scripts link and uses window.open specifically to keep an
opener. The new store helper explicitly reads from window.opener.sessionStorage, confirming reliance
on opener-based transfer.

src/routes/page/agent/[agentId]/agent-components/rules/agent-rule-item.svelte[113-127]
src/lib/helpers/store.js[43-70]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The new tab is intentionally opened with an opener reference so it can read `opener.sessionStorage` and restore the session. Keeping `window.opener` increases exposure to reverse-tabnabbing/opener manipulation if the opened tab ever navigates to untrusted content (e.g., via an XSS, open redirect, or user-driven navigation).

### Issue Context
This behavior is paired with `restoreSessionFromOpener()` which reads `window.opener.sessionStorage`.

### Fix Focus Areas
- src/routes/page/agent/[agentId]/agent-components/rules/agent-rule-item.svelte[113-127]
- src/lib/helpers/store.js[43-74]

### Safer alternatives
Prefer a session-sharing mechanism that doesn’t require opener (so you can use `noopener`):
- Use `localStorage` + `storage` event / `BroadcastChannel` to request/response a session copy.
- Use a short-lived one-time token in the URL (issued server-side) rather than copying sessionStorage.
If opener must be used, consider constraining capabilities (e.g., immediately `window.opener = null` after the child has copied needed values, if feasible in the child page).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View review recommended (1)
4. Deep link lost on errors 🐞 Bug ☼ Reliability
Description
The code-scripts page removes ?agentId= from the URL whenever the current agentId is not found in
agentOptions; if agentOptions fails to load and remains empty, valid deep links will be stripped
during transient errors. This breaks the new deep-linking behavior under network/API failure
conditions.
Code

src/routes/page/agent/code-scripts/+page.svelte[R83-86]

+        if (!agentOptions.some(x => x.value === agentId)) {
+            syncAgentIdToUrl(null);
+            return;
+        }
Evidence
onMount continues to applyAgentIdFromUrl even after loadAgentOptions errors. applyAgentIdFromUrl
then checks membership in agentOptions and navigates away (dropping the query) when the list is
empty.

src/routes/page/agent/code-scripts/+page.svelte[57-68]
src/routes/page/agent/code-scripts/+page.svelte[79-95]
src/routes/page/agent/code-scripts/+page.svelte[97-109]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
On mount, agent options are loaded in a try/catch; regardless of failure, `applyAgentIdFromUrl()` runs and treats any `agentId` as invalid when `agentOptions` is empty, then calls `syncAgentIdToUrl(null)` which drops the query param.

### Issue Context
This defeats deep links like `/page/agent/code-scripts?agentId=...` exactly when options loading fails (e.g., transient backend outage).

### Fix Focus Areas
- src/routes/page/agent/code-scripts/+page.svelte[57-68]
- src/routes/page/agent/code-scripts/+page.svelte[79-95]

### Suggested change
Gate URL validation on successful options load, e.g.:
- Track `agentOptionsLoadedSuccessfully` and in `applyAgentIdFromUrl()` return early if options aren’t loaded.
- Or if options load fails, keep `selectedAgentId = agentId` and defer validation until options are available (don’t call `syncAgentIdToUrl(null)` in the failure case).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can enable the Remediation agent and Qodo fixes findings in a dedicated fix PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +77 to +80
return {
mode: mode.trim() || null,
criteria: text || null
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Criteria whitespace preserved 🐞 Bug ≡ Correctness

normalizeCriteria() checks text.trim() to detect a blank criteria, but returns the untrimmed text,
so a whitespace-only criteria is still persisted whenever mode is set. This can produce semantically
blank-but-present criteria values in agent.rules payloads.
Agent Prompt
### Issue description
`normalizeCriteria()` trims `mode` but returns `criteria: text || null` without trimming. If the user enters only whitespace in Criteria Text while selecting a mode, the criteria object will be saved with whitespace content.

### Issue Context
`fetchRules()` uses `normalizeCriteria()` to build the rules array that is saved back into `agent.rules`.

### Fix Focus Areas
- src/routes/page/agent/[agentId]/agent-components/rules/agent-rule.svelte[66-81]

### Suggested change
Return `criteria: text.trim() || null` (and optionally set `const trimmedText = text.trim()` once) so whitespace-only criteria does not get persisted.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/lib/helpers/store.js
Comment on lines +53 to +56
export function restoreSessionFromOpener() {
if (!browser || openerSessionRestored) return;
openerSessionRestored = true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Opener restore is one-shot 🐞 Bug ☼ Reliability

restoreSessionFromOpener() sets openerSessionRestored=true before copying any keys, so later calls
will not retry copying keys that were missing during the first attempt. This can leave the new tab
without tenant_id/tenant_name (or other keys) if the opener populates them after the first restore
attempt.
Agent Prompt
### Issue description
`restoreSessionFromOpener()` is guarded by a global `openerSessionRestored` flag that is set to `true` before attempting any key copies. If the first call occurs before the opener has populated some keys (e.g., tenant_id/tenant_name set later), subsequent calls will never attempt to copy those missing keys.

### Issue Context
`getUserStore()` calls this only when `user` is absent; `getTenantId()`/`getTenantName()` call it unconditionally, but are blocked after the first attempt.

### Fix Focus Areas
- src/lib/helpers/store.js[40-74]
- src/lib/helpers/store.js[101-128]

### Suggested change
Allow repeated attempts to copy *still-missing* keys without overwriting existing values. For example:
- Remove the global one-shot flag entirely, or
- Replace it with per-key tracking (e.g., `restoredKeys` set) and only skip keys already present or already successfully restored, or
- Only set `openerSessionRestored=true` after a same-origin opener is confirmed and the copy loop has run, and allow retrying when any of `[userKey, tenantKey, tenantNameKey]` remains missing.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +123 to +126
if (!codeScriptUrl) return;

e.preventDefault();
window.open(codeScriptUrl, '_blank');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

3. Opener kept for new tab 🐞 Bug ⛨ Security

openCodeScripts() uses window.open(url, '_blank') specifically to preserve window.opener for session
restoration, which means the opened tab can navigate the opener (reverse-tabnabbing/opener
manipulation) if it ever reaches attacker-controlled content. This is a security tradeoff introduced
by relying on opener for credential/session transfer.
Agent Prompt
### Issue description
The new tab is intentionally opened with an opener reference so it can read `opener.sessionStorage` and restore the session. Keeping `window.opener` increases exposure to reverse-tabnabbing/opener manipulation if the opened tab ever navigates to untrusted content (e.g., via an XSS, open redirect, or user-driven navigation).

### Issue Context
This behavior is paired with `restoreSessionFromOpener()` which reads `window.opener.sessionStorage`.

### Fix Focus Areas
- src/routes/page/agent/[agentId]/agent-components/rules/agent-rule-item.svelte[113-127]
- src/lib/helpers/store.js[43-74]

### Safer alternatives
Prefer a session-sharing mechanism that doesn’t require opener (so you can use `noopener`):
- Use `localStorage` + `storage` event / `BroadcastChannel` to request/response a session copy.
- Use a short-lived one-time token in the URL (issued server-side) rather than copying sessionStorage.
If opener must be used, consider constraining capabilities (e.g., immediately `window.opener = null` after the child has copied needed values, if feasible in the child page).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +83 to +86
if (!agentOptions.some(x => x.value === agentId)) {
syncAgentIdToUrl(null);
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

4. Deep link lost on errors 🐞 Bug ☼ Reliability

The code-scripts page removes ?agentId= from the URL whenever the current agentId is not found in
agentOptions; if agentOptions fails to load and remains empty, valid deep links will be stripped
during transient errors. This breaks the new deep-linking behavior under network/API failure
conditions.
Agent Prompt
### Issue description
On mount, agent options are loaded in a try/catch; regardless of failure, `applyAgentIdFromUrl()` runs and treats any `agentId` as invalid when `agentOptions` is empty, then calls `syncAgentIdToUrl(null)` which drops the query param.

### Issue Context
This defeats deep links like `/page/agent/code-scripts?agentId=...` exactly when options loading fails (e.g., transient backend outage).

### Fix Focus Areas
- src/routes/page/agent/code-scripts/+page.svelte[57-68]
- src/routes/page/agent/code-scripts/+page.svelte[79-95]

### Suggested change
Gate URL validation on successful options load, e.g.:
- Track `agentOptionsLoadedSuccessfully` and in `applyAgentIdFromUrl()` return early if options aren’t loaded.
- Or if options load fails, keep `selectedAgentId = agentId` and defer validation until options are available (don’t call `syncAgentIdToUrl(null)` in the failure case).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@iceljc
iceljc merged commit ce41184 into SciSharp:main Aug 12, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant